#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>

#define COMP_LINEAR 1
#define COMP_CYCLE 2

#define INPUT_FILE "input_test.txt"
#define DEBUG_ALWAYS_ON 0
#define DEBUG (dcj_args[OPT_DEBUG] || DEBUG_ALWAYS_ON)
#define VERBOSE dcj_args[OPT_VERBOSE]
#define LOG dcj_args[OPT_LOG]


#define OPT_RESTRICT_MODEL 0
#define OPT_GENERATE_INPUT 1
#define OPT_INPUT_FILE 2
#define OPT_OUTPUT_FILE 3
#define OPT_VERBOSE 4
#define OPT_LOG 5
#define OPT_DEBUG 6

int dcj_args [] = {0, 0, 0, 0, 0, 0, 0};

/* Tipos de operação */
enum OPERATION {
    invalid = 0,
    path_transloc,
    path_fusion,
    path_fission,
    inversion,
    excision,
    integration,
    circularization,
    linearization,
    cycle_inversion,
    cycle_fusion,
    cycle_fission
};
char* operation_names[] = {
    "Invalid", 
    "Path Translocation",
    "Path Fusion",
    "Path Fission",
    "Inversion",
    "Excision",
    "Integration",
    "Circularization",
    "Linearization",
    "Cycle Inversion",
    "Cycle Fusion",
    "Cycle Fission"
};

/*
 * Parseia os argumentos passados ao programa.
 */
void parse_args(int argc, char* argv[]);

/*
 * Mostra o help e sai do programa.
 */
void help();

/* Popula os grafos a partir do arquivo de entrada */
void read_input_file(char* file);

/*
 * Preenche as tabelas que representam o grafo de adjacências de acordo
 * com a linha lida da entrada padrão, no formato "+1 -2 | -3 +4 ...".
*/
void read_adjacency_graph(int** table_loc, int** table_adj, unsigned* table_len);

/*
 * Função que realiza o procedimento de DCJ
 */
void dcj();

/*
 * Verifica se o gene 'g' está em uma componente circular no genoma A.
 * Retorna 'g' caso o gene faça parte de uma componente circular.
 * Retorna 0 caso contrário.
 */
int check_circular_component(int g);

/*
 * Obtem em qual adjacencia a cabeça/cauda do gene g está, em relação a tabela table.
 */
int get_gene_location(int g, int **table);

/*
 * Atualiza a localização de um gene na tabela.
 */
void set_gene_location(int g, int new_location, int **table);

/*
 * Verifica se uma adjacencia a é um telomero na tabela table
 */
int is_telomere(int a, int **table);

/*
 * Popula o vetor de componentes, marcando-os como lineares.
 */
void populate_component_type();

/*
 * Verifica qual o tipo de operação ocorreu.
 */
enum OPERATION find_operation();

/*
 * Aloca memória para as tabelas de adjacência e localização e as inicializa.
 */
void init_tables();

/*
 * Imprime a tabela de adjacências do genoma A. Funciona apenas em modo debug.
 */
void print_adjacency_table();

/*
 * Retorna um elemento da adjacência seguinte àquela à qual pertence
 * o gene indicado.
 */
int get_next_element(int gene);

/*
 * Retorna o gene que pertence à mesma adjacência que 'gene' ou 0 caso
 * este pertença a um telômero.
 */
int get_other_gene(int gene, int **table_loc, int **table_adj);

/*
 * Atualiza a adjacência do genoma A que contém o gene indicado.
 */
void update_adjacency(int gene, int e1, int e2);

/* Globals */
unsigned genome_length;
int **genome1_location;
int **genome1_adjacency;
int **genome2_location;
int **genome2_adjacency;
int *genome1_component_type;

unsigned genome1_adjacency_length;
unsigned genome2_adjacency_length;

unsigned distance;

int main (int argc, char **argv) {
    // Inicializa as tabelas
    init_tables();
    
    //Parse dos argumentos do programa.
    parse_args(argc, argv);

    if(dcj_args[OPT_GENERATE_INPUT] == 0) { //Só vamos ler da entrada se o usuário não pediu para que uma entrada fosse gerada automaticamente.
        // Popula genome_length
        scanf("%d\n", &genome_length);
    
        // Cria o grafo para o primeiro genoma
        read_adjacency_graph(genome1_location, genome1_adjacency, &genome1_adjacency_length);

        // Cria o grafo para o segundo genoma
        read_adjacency_graph(genome2_location, genome2_adjacency, &genome2_adjacency_length);
    }
    
    populate_component_type();

    // Se estiver em modo debug, imprime a tabela de adjacências do genoma A
    print_adjacency_table();
    
    // DCJ
    dcj();
    
    if (VERBOSE) {
        unsigned i, j;
        
        printf("\n");
        
        // Imprime tabela de adjacências do genoma B
        printf("Genome A - Adjacency Table: \n");
        for (i = 0; i < 2; i++) {
            for (j = 0; j < genome1_adjacency_length; j++) {
                printf("%3d ", genome1_adjacency[i][j]);
            }
            printf("\n");
        }
        
        // Imprime tabela de adjacências do genoma B
        printf("Genome B - Adjacency Table: \n");
        for (i = 0; i < 2; i++) {
            for (j = 0; j < genome2_adjacency_length; j++) {
                printf("%3d ", genome2_adjacency[i][j]);
            }
            printf("\n");
        }
    }

    if (DEBUG) {
        unsigned i, j;
        
        // Imprime tabela de localizações do genoma A
        printf("Genome A - Location Table: \n");
        for (i = 0; i < 3; i++) {
            for (j = 1; j <= genome_length; j++) {
                printf("%2d ", genome1_location[i][j]);
            }
            printf("\n");
        }
        printf("\n\n");
        
        // Imprime tabela de adjacências do genoma B
        printf("Genome B - Adjacency Table: \n");
        for (i = 0; i < 2; i++) {
            for (j = 0; j < genome2_adjacency_length; j++) {
                printf("%3d ", genome2_adjacency[i][j]);
            }
            printf("\n");
        }
        printf("\n\n");

        // Imprime tabela de localizações do genoma B
        printf("Genome B - Location Table: \n");
        for (i = 0; i < 2; i++) {
            for (j = 1; j <= genome_length; j++) {
                printf("%2d ", genome2_location[i][j]);
            }
            printf("\n");
        }
        printf("\n\n");
    }
    
    printf("\nDistance: %d\n", distance);
    
    return 0;
}

void read_adjacency_graph(int** table_loc, int** table_adj, unsigned* table_len) {
    int pos = 0, comp = 1;
    int number;
    char signal;
    
    for (scanf("%c", &signal); signal != '\n'; scanf("%c", &signal)) {
        if (signal == '+') {
            scanf("%d", &number);
            
            // Guarda "cauda" do gene
            table_adj[1][pos] = -1 * number;
            table_loc[1][number] = pos;
            
            pos++;

            // Guarda "cabeça" do gene
            table_adj[0][pos] = number;
            table_loc[0][number] = pos;
            
            // Guarda o componente no qual o gene se encontra
            table_loc[2][number] = comp;
        }
        else if (signal == '-') {
            scanf("%d", &number);

            // Guarda "cabeça" do gene
            table_adj[1][pos] = number;
            table_loc[0][number] = pos;
            
            pos++;

            // Guarda "cauda" do gene
            table_adj[0][pos] = -1 * number;
            table_loc[1][number] = pos;
            
            // Guarda o componente no qual o gene se encontra
            table_loc[2][number] = comp;
        } 
        else if (signal == '|') {
            // Como as tabelas já estão inicializadas como 0, não é preciso 
            // fazer mais nada.
            pos++;
            
            // Houve uma troca de componente, incrementa o indice de componentes
            comp++;
        }
    }
    
    *table_len = pos + 1;
}

void dcj() {
    int gen_p, gen_q, loc_u, loc_v, i;
    int u_e1, u_e2, v_e1, v_e2; //Elementos de u e v no genoma A
    int v_freepos;

    // Para cada adjacência {p, q} no genoma alvo
    for (i = 0; (genome2_adjacency[0][i] != 0 || genome2_adjacency[1][i] != 0); i++) {
        gen_p = genome2_adjacency[0][i];
        gen_q = genome2_adjacency[1][i];
        
        if (gen_p == 0 || gen_q == 0) continue; //Não iremos tratar telomeros agora.

        //Seja u, o elemento do genoma origem que contem p:
        loc_u = get_gene_location(gen_p, genome1_location);

        //Seja v, o elemento do genoma origem que contem q:
        loc_v = get_gene_location(gen_q, genome1_location);

        printf("##### loc_u: %d loc_v: %d\n", loc_u, loc_v);

        //Se u for igual a v, não faz nada.
        if (loc_u == loc_v) continue;
        
        // Incremento da distancia para modelo nao-restrito, caso adjacencias
        distance++;

        u_e1 = genome1_adjacency[0][loc_u];
        u_e2 = genome1_adjacency[1][loc_u];
        v_e1 = genome1_adjacency[0][loc_v];
        v_e2 = genome1_adjacency[1][loc_v];

        //Substitua u por {p, q}
        genome1_adjacency[0][loc_u] = gen_p;
        genome1_adjacency[1][loc_u] = gen_q;

        if (gen_q < 0) { //Estamos vendo a "cauda" de q:
            genome1_location[1][gen_q * -1] = loc_u;
        } else { //Estamos vendo a "cabeça" de p:
            genome1_location[0][gen_q] = loc_u;
        }

        //Substitua v por (u \ {p}) U (v \ {q})
        if (v_e1 == gen_q) { //(v \ {q}) é o segundo elemento de v
            v_freepos = 0;
        } else { //(v \ {q}) é o segundo elemento de v
            v_freepos = 1;
        }

        if (u_e1 == gen_p) { //(u \ {p}) é o segundo elemento de u
            genome1_adjacency[v_freepos][loc_v] = u_e2;

            if (u_e2 < 0) { //Estamos vendo a "cauda" de u_e2
                genome1_location[1][u_e2 * -1] = loc_v;
            } else if (u_e2 > 0) { //Estamos vendo a "cabeça" de u_e2:
                genome1_location[0][u_e2] = loc_v;
            }

        } else { //(u \ {p}) é o primeiro elemento de u
            genome1_adjacency[v_freepos][loc_v] = u_e1;

            if (u_e1 < 0) { //Estamos vendo a "cauda" de u_e1:
                genome1_location[1][u_e1 * -1] = loc_v;
            } else if (u_e1 > 0) { //Estamos vendo a "cabeça" de u_e1:
                genome1_location[0][u_e1] = loc_v;
            }
        }
        
        int new_u[2];
        int new_v[2];
        
        new_u[0] = genome1_adjacency[0][loc_u];
        new_u[1] = genome1_adjacency[1][loc_u];
        new_v[0] = genome1_adjacency[0][loc_v];
        new_v[1] = genome1_adjacency[1][loc_v];
        
        // Output da operação realizada
        if (VERBOSE || DEBUG) {
            printf("\nAdjacency loop %d: (%d,%d)\n", i, gen_p, gen_q);
            printf("u: (%d,%d), v: (%d,%d)\n", u_e1, u_e2, v_e1, v_e2);
            printf("new u: (%d,%d), new v: (%d,%d)\n", new_u[0], new_u[1], new_v[0], new_v[1]);
        }
        print_adjacency_table();
        
        // Detecta o tipo de operação que ocorreu.
        // enum OPERATION op = find_operation(u_e1, u_e2, v_e1, v_e2);
        // printf("\nOperation: %s\n", operation_names[op]);
        
        // Tratamento de modelo restrito, se o modelo restrito estiver ativado.
        if (dcj_args[OPT_RESTRICT_MODEL] == 1) {
            // Verifica se criou um ciclo com algum dos itens alterados.
            int cycle = check_circular_component(new_u[0]);
            if (!cycle) cycle = check_circular_component(new_v[0]);
            
            if (cycle) {
                if (VERBOSE || DEBUG) {
                    printf("\nOperation: %s\n", operation_names[excision]);
                }
                
                // Incremento da distancia para modelo restrito
                distance++;
                
                // Tratamento de modelo restrito

                // Itera sobre as adjacências do ciclo até encontrar uma que
                // possa ser quebrada.
                int current = cycle;
                while (1) {
                    // Adjacências do genoma A
                    int u[2], v[2], u2[2], v2[2];
                    u[0] = current;
                    u[1] = get_other_gene(u[0], genome1_location, genome1_adjacency);
                    
                    // Adjacências do genoma B
                    int p[2], q[2];
                    p[0] = current;
                    p[1] = get_other_gene(p[0], genome2_location, genome2_adjacency);
                    
                    // Se a adjacência u do genoma A que contém o gene 'current' 
                    // é diferente da adjacência p de B que contém o mesmo gene,
                    // o ciclo pode ser quebrado neste ponto.
                    if (u[1] != p[1]) {
                        // Quebra do ciclo
                        
                        // Mudança na adjacência u:
                        // u2 = (u[0], p[1])
                        u2[0] = u[0];
                        u2[1] = p[1];
                        
                        if (p[1] != 0) {
                            // Caso 1: u2 não é telômero
                            // Operação: Integração
                            // Nova adjacência:
                            // v2 = (v[1], u[1])
                            
                            // Adjacência v à qual pertence p[1] no genoma A:
                            v[0] = p[1];
                            v[1] = get_other_gene(v[0], genome1_location, genome1_adjacency);
                            
                            if (DEBUG) {
                                printf("Restricted model: v(%d, %d)\n", v[0], v[1]);
                            }
                            
                            v2[0] = v[1];
                            v2[1] = u[1];
                            
                        } else {
                            // Caso 2: u2 é telômero

                            // Adjacência q à qual pertence u[1] no genoma B:
                            q[0] = u[1];
                            q[1] = get_other_gene(q[0], genome2_location, genome2_adjacency);
                            
                            // Adjacência v à qual pertence p[1] no genoma A:
                            v[0] = q[1];
                            v[1] = get_other_gene(v[0], genome1_location, genome1_adjacency);

                            if (DEBUG) {
                                printf("Restricted model: q(%d, %d) v(%d, %d)\n", q[0], q[1], v[0], v[1]);
                            }
                            
                            v2[0] = q[1];
                            v2[1] = u[1];
                            
                            // Se v for telômero em A:
                            int is_telomere = (v[0] == 0) ^ (v[1] == 0);
                            if (is_telomere) {                            
                                // Caso 2a: Ciclo pode se ligar a um telômero de outro
                                // cromossomo
                                // Operação: Integração
                                // Nova adjacência:
                                // v2 = (q[1], u[1])
                                if (VERBOSE || DEBUG) {
                                    printf("\nOperation: %s\n", operation_names[integration]);
                                }
                            } else {
                                // Caso 2b: Ciclo ganha 2 telômeros. Corte na adjacencia vazia v.
                                // Operação: Linearização
                                // Nova adjacência:
                                // v2 = (u[1], 0)
                                if (VERBOSE || DEBUG) {
                                    printf("\nOperation: %s\n", operation_names[linearization]);
                                }
                                
                                // Criando nova adjacência
                                genome1_adjacency_length++;
                            }
                        }
                        
                        // Grava a adjacência u nas tabelas.
                        update_adjacency(u[0], u2[0], u2[1]);
                        
                        // Grava a adjacência v nas tabelas.
                        update_adjacency(v[1], v2[0], v2[1]);
                        
                        if (DEBUG) {
                            printf("In Genome A: u = (%d, %d)\n", u[0], u[1]);
                            printf("In Genome B: p = (%d, %d)\n", p[0], p[1]);
                            
                            printf("---------------------------------\n");
                            printf("Mudança: u2 = (%d, %d)\n", u2[0], u2[1]);
                            printf("Nova: v2 = (%d, %d)\n", v2[0], v2[1]);
                            printf("---------------------------------\n");
                        }
                        
                        // Ciclo foi quebrado.
                        break;
                    }
                    else {
                        if (DEBUG) {
                            printf("Equal: (%d, %d)\n", u[0], u[1]);
                        }
                    }
                    
                    current = get_next_element(current);
                    if (current == cycle) {
                        if (DEBUG) {
                            printf("Deu bode!\n");
                        }
                        break;
                    }
                    
                    
                }
            }
        }
        
        print_adjacency_table();
    }

    // Para cada telomero no genoma alvo
    for (i = 0; (genome2_adjacency[0][i] != 0 || genome2_adjacency[1][i] != 0); i++) {
        gen_p = genome2_adjacency[0][i];
        gen_q = genome2_adjacency[1][i];
        
        if (gen_p != 0 && gen_q != 0) continue; //Já tratamos os casos de adjacencia.
    
        if (gen_p == 0) { //Telomeros que iniciam uma seq:
            //Seja u, o elemento do genoma origem que contem q:
            int u_p; //Primeiro elemento da adjacencia u.
            int u_q; //Segundo elemento da adjacencia u.

            loc_u = get_gene_location(gen_q, genome1_location);

            u_p = genome1_adjacency[0][loc_u];
            u_q = genome1_adjacency[1][loc_u];

            //Se u for adjacencia:
            if (genome1_adjacency[0][loc_u] != 0 && genome1_adjacency[1][loc_u] != 0) {
                // Incremento da distancia para modelo nao-restrito, caso para telomeros
                distance++;
                
                // Substitui u por {q} e {u \ {q}}
                genome1_adjacency[0][loc_u] = 0;

                //Cria um novo telomero de termino com o resto de u
                genome1_adjacency[0][genome1_adjacency_length] = 0;
                genome1_adjacency[1][genome1_adjacency_length] = u_p;

                if (u_p < 0) { //"Cauda" do gene p:
                    genome1_location[1][u_p * -1] = genome1_adjacency_length;
                } else { //"Cabeça" do gene p:
                    genome1_location[0][u_p] = genome1_adjacency_length;
                }

                genome1_adjacency_length++;

                // Output da operação realizada
                if (VERBOSE || DEBUG) {
                    printf("\nTelomere loop %d: (%d,%d)\n", i, gen_p, gen_q);
                    printf("u: (%d,%d)\n", u_p, u_q);
                    printf("new u: (%d,%d), new adjacency: (%d,%d)\n",
                           genome1_adjacency[0][loc_u],
                           genome1_adjacency[1][loc_u],
                           genome1_adjacency[0][genome1_adjacency_length-1],
                           genome1_adjacency[1][genome1_adjacency_length-1]);
                }
                
                // Operação é uma fissão
                if (VERBOSE || DEBUG) {
                    printf("\nOperation: %s\n", operation_names[path_fission]);
                }
            }
            //Se u é telômero, mas não inicia a sequência
            else if (genome1_adjacency[0][loc_u] != 0) {
                genome1_adjacency[1][loc_u] = genome1_adjacency[0][loc_u];
                genome1_adjacency[0][loc_u] = 0;

                // Output da operação realizada
                if (VERBOSE || DEBUG) {
                    printf("\nTelomere loop %d: (%d,%d)\n", i, gen_p, gen_q);
                    printf("u: (%d,%d)\n", u_p, u_q);
                    printf("new u: (%d,%d)\n", genome1_adjacency[0][loc_u], genome1_adjacency[1][loc_u]);
                }
            }
    
        } else if (gen_q == 0) { //Telomeros que terminam uma seq:
            //Seja u, o elemento do genoma origem que contem p:
            int u_p; //Primeiro elemento da adjacencia u.
            int u_q; //Segundo elemento da adjacencia u.

            loc_u = get_gene_location(gen_p, genome1_location);

            u_p = genome1_adjacency[0][loc_u];
            u_q = genome1_adjacency[1][loc_u];

            //Se u for adjacencia:
            if (genome1_adjacency[0][loc_u] != 0 && genome1_adjacency[1][loc_u] != 0) {
                // Substitui u por {p} e {u \ {p}}
                genome1_adjacency[1][loc_u] = 0;

                //Cria um novo telomero de inicio com o resto de u
                genome1_adjacency[0][genome1_adjacency_length] = u_q;
                genome1_adjacency[1][genome1_adjacency_length] = 0;

                if (u_q < 0) { //"Cauda" do gene q:
                    genome1_location[1][u_q * -1] = genome1_adjacency_length;
                } else { //"Cabeça" do gene q:
                    genome1_location[0][u_q] = genome1_adjacency_length;
                }

                genome1_adjacency_length++;

                // Output da operação realizada
                if (VERBOSE || DEBUG) {
                    printf("\nTelomere loop %d: (%d,%d)\n", i, gen_p, gen_q);
                    printf("u: (%d,%d)\n", u_p, u_q);
                    printf("new u: (%d,%d), new adjacency: (%d,%d)\n",
                           genome1_adjacency[0][loc_u],
                           genome1_adjacency[1][loc_u],
                           genome1_adjacency[0][genome1_adjacency_length-1],
                           genome1_adjacency[1][genome1_adjacency_length-1]);
                }
                
                // Operação é uma fissão
                if (VERBOSE || DEBUG) {
                    printf("\nOperation: %s\n", operation_names[path_fission]);
                }
            }
            //Se u é telômero, mas não termina a sequência
            else if (genome1_adjacency[1][loc_u] != 0) {
                genome1_adjacency[0][loc_u] = genome1_adjacency[1][loc_u];
                genome1_adjacency[1][loc_u] = 0;

                // Output da operação realizada
                if (VERBOSE || DEBUG) {
                    printf("\nTelomere loop %d: (%d,%d)\n", i, gen_p, gen_q);
                    printf("u: (%d,%d)\n", u_p, u_q);
                    printf("new u: (%d,%d)\n", genome1_adjacency[0][loc_u], genome1_adjacency[1][loc_u]);
                }                
            }
        }
        
        print_adjacency_table();
    }
}

int check_circular_component(int g) {
    int dest, prox;
    
    if (g == 0) return 0; //Um gene 0 indica um telômero.
    
    if (g < 0) g *= -1; //Vamos começar a busca sempre pela "cabeça" do gene.

    //Inicialmente começamos na cabeça e queremos chegar nele de novo.
    dest = g;
    prox = g;

    do {
        // Encontra o próximo gene
        prox = get_next_element(prox);
    } while(prox != dest && prox != 0); //Para se chegarmos de volta a cabeça do nosso genoma inicial ou se nossa sequencia acabar (telomero)

    //Se chegamos ao final esperado, estamos em um cromossomo circular.
    if (prox == dest) return g;
    
    //Senão, estamos em uma componente linear.
    return 0;
}

int get_gene_location(int g, int **table) {

    if (g > 0) { //Estamos vendo a "cabeça" de g:
        return table[0][g];
    } else if (g < 0) { //Estamos vendo a "cauda" de g:
        return table[1][g * -1];
    }

    return -1; //Erro
}

void set_gene_location(int g, int new_location, int **table) {
    if (g > 0) { //Estamos vendo a "cabeça" de g:
        table[0][g] = new_location;
    } else if (g < 0) { //Estamos vendo a "cauda" de g:
        table[1][g * -1] = new_location;
    }
}

int is_telomere(int a, int **table) {

    if (a < 0) return -1; //Erro

    if (table[0][a] == 0 && table[1][a] == 0) return 0; //Adjacência vazia.    
    if (table[0][a] != 0 && table[1][a] != 0) return 0; //Adjacência completa.

    return 1; //É telomero.
}

void populate_component_type() {
    unsigned int i;
    
    for (i = 1; i <= genome_length; i++){
        genome1_component_type[genome1_location[2][i]] = COMP_LINEAR;
    }
}

enum OPERATION find_operation() {
    int comp_u_e1, comp_v_e1;

    // Verifica se o gene u está em um ciclo
    // if (check_circular_component(u_e1) == 0) { comp_u_e1 = COMP_LINEAR; }
    // else { comp_u_e1 = COMP_CYCLE; }
    
    // if (check_circular_component(v_e1) == 0) { comp_v_e1 = COMP_LINEAR; }
    // else { comp_v_e1 = COMP_CYCLE; }

    // Operação ocorreu entre dois paths
    if (comp_u_e1 == COMP_LINEAR && comp_v_e1 == COMP_LINEAR) {
        if (DEBUG) { printf("Operation: path / path.\n"); }

        if (0) {
            return path_transloc;
        } 
        else if (0) {
            return path_fusion;
        } 
        else if (0) {
            return path_fission;
        }
    }
    // Operação ocorreu entre um path e um cycle
    else if ((comp_u_e1 == COMP_LINEAR && comp_v_e1 == COMP_CYCLE) ||
             (comp_u_e1 == COMP_CYCLE && comp_v_e1 == COMP_LINEAR)) {
        if (DEBUG) { printf("Operation: path / cycle.\n"); }
        
        if (0) {
            return inversion;
        }
        else if (0) {
            return excision;
        }
        else if (0) {
            return integration;
        }
        else if (0) {
            return circularization;
        }
        else if (0) {
            return linearization;
        }
    }
    // Operação ocorreu entre dois cycles
    else if (comp_u_e1 == COMP_CYCLE && comp_v_e1 == COMP_CYCLE) {
        if (DEBUG) { printf("Operation: cycle / cycle.\n"); }
        
        if (0) {
            return cycle_inversion;
        }
        else if (0) {
            return cycle_fusion;
        }
        else if (0) {
            return cycle_fission;
        }
    }
    
    return invalid;
}

void init_tables() {
    int i;
    
    // Inicializa distancia
    distance = 0;

    // Aloca memórias das tabelas
    genome1_location = (int**) malloc(sizeof(int*) * 3);
    genome2_location = (int**) malloc(sizeof(int*) * 3);
    genome1_adjacency = (int**) malloc(sizeof(int *) * 2);
    genome2_adjacency = (int**) malloc(sizeof(int *) * 2);

    genome1_component_type = (int*) malloc(sizeof(int) * 10240);
    memset(genome1_component_type, 0, sizeof(int) * 10240);

    for(i = 0; i < 2; i++) {
        genome1_adjacency[i] = (int*) malloc(sizeof(int) * 20480);
        genome2_adjacency[i] = (int*) malloc(sizeof(int) * 20480);

        // Inicializa as tabelas com 0
        memset(genome1_adjacency[i], 0, sizeof(int) * 20480);
        memset(genome2_adjacency[i], 0, sizeof(int) * 20480);
    }
    
    for(i = 0; i < 3; i++) {
        genome1_location[i]  = (int*) malloc(sizeof(int) * 10240);
        genome2_location[i]  = (int*) malloc(sizeof(int) * 10240);
        
        // Inicializa as tabelas com 0
        memset(genome1_location[i], 0, sizeof(int) * 10240);
        memset(genome2_location[i], 0, sizeof(int) * 10240);
    }
}

void parse_args(int argc, char* argv[]) {
    int option_index, option;

    static struct option long_options[] = {
        {"restrict",   no_argument,       0, 'm'},
        {"inputfile",  required_argument, 0, 'i'},
        {"outputfile", required_argument, 0, 'o'},
        {"help",       no_argument,       0, 'h'},
        {"verbose",    no_argument,       0, 'v'},
        {"log",        no_argument,       0, 'l'},
        {"debug",      no_argument,       0, 'd'},
        {0,            0,                 0,  0 }};

    while(1)
    {
        option_index = 0;
        option = getopt_long(argc, argv, "mi:o:hvld", long_options, &option_index);

        if(option == -1) break;

        switch(option) {
            case 'm':
                dcj_args[OPT_RESTRICT_MODEL] = 1;
                if(VERBOSE || DEBUG) printf("Activating rectricted model mode.\n");
                break;
            case 'i':
                if(VERBOSE || DEBUG) printf("Reading input from '%s'\n", optarg);
                freopen(optarg, "r", stdin);
                break;
            case 'o':
                if(VERBOSE || DEBUG) printf("Writing all output to '%s'\n", optarg);
                freopen(optarg, "w", stdout);
                break;
            case 'h':
                help();
                break;
            case 'v':
                dcj_args[OPT_VERBOSE] = 1;
                if(VERBOSE || DEBUG) printf("Activating verbose mode.\n");
                break;
            case 'l':
                dcj_args[OPT_LOG] = 1;
                if(VERBOSE || DEBUG) printf("Activating log mode.\n");
                break;
            case 'd':
                dcj_args[OPT_DEBUG] = 1;
                if(VERBOSE || DEBUG) printf("Activating debug mode.\n");
                break;
        }
            
    }

}

void print_adjacency_table() {
    if (DEBUG) {
        printf("\nGenome A - Adjacency Table: \n");
        
        unsigned k, l;
        for (k = 0; k < 2; k++) {
            for (l = 0; l < genome1_adjacency_length; l++) {
                printf("%3d ", genome1_adjacency[k][l]);
            }
            printf("\n");
        }
        printf("\n");
    }
}

int get_next_element(int gene) {
    // Encontra posição do gene
    int gene_location = get_gene_location(gene, genome1_location);

    // O proximo será a outra extremidade do outro elemento que está na mesma
    // adjacência do gene, assim prosseguindo no cromossomo.
    if (genome1_adjacency[0][gene_location] == gene) {
        return genome1_adjacency[1][gene_location] * -1;
    } else {
        return genome1_adjacency[0][gene_location] * -1;
    }
}

int get_other_gene(int gene, int **table_loc, int **table_adj) {
    int gene_loc = get_gene_location(gene, table_loc);
    
    int adjacency[2];
    adjacency[0] = table_adj[0][gene_loc];
    adjacency[1] = table_adj[1][gene_loc];
    
    if (adjacency[0] == gene) {
        return adjacency[1];
    } else {
        return adjacency[0];
    }
}

void update_adjacency(int gene, int e1, int e2) {
    int location;
    
    if (gene != 0) {
        location = get_gene_location(gene, genome1_location);
        int other = get_other_gene(gene, genome1_location, genome1_adjacency);

        // Marca a localização dos genes da adjacência antiga como inválida
        printf("loc: %d other %d\n", gene, other);
        //set_gene_location(gene, -1, genome1_location);
        //set_gene_location(other, -1, genome1_location);
        
    } else {
        // Adjacência é nova
        location = genome1_adjacency_length - 1;
    }
    
    // Atualiza tabela de adjacências
    genome1_adjacency[0][location] = e1;
    genome1_adjacency[1][location] = e2;
    
    // Atualiza tabela de localização
    set_gene_location(e1, location, genome1_location);
    set_gene_location(e2, location, genome1_location);
}

void help() {

    printf("Project Name: cos886-dcj\n");
    printf("Homepage (and source repo): http://code.google.com/p/cos886-dcj/\n\n");

    printf("Arguments:\n");
    printf("\t-m: Compute with the Restricted DCJ Model.\n");
    printf("\t-i file: Read input from file.\n");
    printf("\t-o file: Write output to file.\n");
    printf("\t-v: Verbose mode.\n");
    printf("\t-d: Debug mode (but be careful... 'cause some output willn't make any sense).\n");
    printf("\t-l: Log mode, write a bunch of useful (or not) information to log.txt.\n");
    printf("\t-h: This help text ;)\n\n");

    exit(1);
}
